package com.jsjvm.client.execution;

import java.util.Arrays;
import java.util.LinkedList;

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.jsjvm.client.classparser.JClass;
import com.jsjvm.client.classparser.JType;
import com.jsjvm.client.classparser.JAttribute.CodeAttribute.ExceptionDescriptor;
import com.jsjvm.client.classparser.JClass.Status;
import com.jsjvm.client.classparser.JMember.JMethod;
import com.jsjvm.client.classparser.jconstants.JBaseConstant;
import com.jsjvm.client.classparser.jconstants.JClassConstant;
import com.jsjvm.client.classparser.jconstants.JMemberConstant;
import com.jsjvm.client.classparser.jconstants.JStringConstant;
import com.jsjvm.client.dbg.BreakpointManager;
import com.jsjvm.client.dbg.DbgUtil;
import com.jsjvm.client.execution.JClassLoader.ClassLoadedListener;
import com.jsjvm.client.execution.JClassLoader.JClassNotLoadedException;
import com.jsjvm.client.execution.objrepr.ArrayRepr;
import com.jsjvm.client.execution.objrepr.ObjectFactory;
import com.jsjvm.client.execution.objrepr.ObjectRepr;
import com.jsjvm.client.execution.objrepr.StaticMembers;
import com.jsjvm.client.util.DataInputStream;
import com.jsjvm.shared.jdwp.events.JDWPEvent;
import com.jsjvm.shared.jdwp.events.JDWPSingleStepEvent;
import com.jsjvm.shared.jdwp.events.JDWPVMExitEvent;
import com.jsjvm.shared.jdwp.events.JDWPVMStartEvent;
import com.jsjvm.shared.vmcmds.VMCommand;

/**
 * 
 * Execution engine with lazy class loading and limited support for JSNI
 * 
 * The class loading process is similar with lazy memory allocation in modern
 * operating systems. When the program wants to use a page, it generates a
 * SEGFAULT, the page is loaded and the program resumed.
 * 
 * In our case, we generate a ClassNotLoadedException, and we resume executing
 * the program in the exception handler.
 * 
 */
public class ExecutionEngine extends Stack {
	public static String BOOT_METHOD_DESCRIPTOR = "main([Ljava/lang/String;)I";

	private JClassLoader jcl = JClassLoader.getInstance();

	// Next instruction to be executed
	private int pc = -1;
	private JMethod m = null;
	private JClass crtClass = null;

	public void execute() {
		int skip, index, addr, ppc = 0, mask = 0;
		Integer i;
		Long l;
		Float f;
		Double d;
		try {
			while (true) {
				ppc = pc;
				mask = skip = 0;
				byte opcode = (byte) nextByte();

				switch (opcode) {
				case OPCodes.OP_nop:
					break;
				case OPCodes.OP_aconst_null:
					push(null);
					break;
				case OPCodes.OP_iconst_m1:
				case OPCodes.OP_iconst_0:
				case OPCodes.OP_iconst_1:
				case OPCodes.OP_iconst_2:
				case OPCodes.OP_iconst_3:
				case OPCodes.OP_iconst_4:
				case OPCodes.OP_iconst_5:
					pushi(opcode - OPCodes.OP_iconst_m1 - 1);
					break;
				case OPCodes.OP_lconst_0:
				case OPCodes.OP_lconst_1:
					pushl((long) (opcode - OPCodes.OP_lconst_0));
					break;
				case OPCodes.OP_fconst_0:
				case OPCodes.OP_fconst_1:
				case OPCodes.OP_fconst_2:
					pushf((float) (opcode - OPCodes.OP_fconst_0));
					break;
				case OPCodes.OP_dconst_0:
				case OPCodes.OP_dconst_1:
					pushd((double) (opcode - OPCodes.OP_dconst_0));
					break;
				case OPCodes.OP_bipush:
					pushi((int) (byte) (int) nextByte());
					break;
				case OPCodes.OP_sipush:
					pushi((int) (short) (int) nextShort());
					break;

				case OPCodes.OP_ldc_w:
				case OPCodes.OP_ldc2_w:
				case OPCodes.OP_ldc: {
					index = nextByte();
					if (opcode != OPCodes.OP_ldc)
						index = (index << 8) + nextByte();
					JBaseConstant cst = crtClass.getBaseConstant(index);

					/*
					 * If the String object is not created, create it and
					 * restart the instruction.
					 */
					if (cst.getValue() == null) {
						pc = ppc;
						initString((JStringConstant) cst);
						break;
					}

					push(cst.getValue(), cst.getSize() - 1);
					break;
				}

					// load / store
				case OPCodes.OP_lload:
				case OPCodes.OP_dload:
					skip = 1;
				case OPCodes.OP_iload:
				case OPCodes.OP_fload:
				case OPCodes.OP_aload:
					push(loadLocal(nextByte()), skip);
					break;

				case OPCodes.OP_lload_0:
				case OPCodes.OP_lload_1:
				case OPCodes.OP_lload_2:
				case OPCodes.OP_lload_3:
				case OPCodes.OP_dload_0:
				case OPCodes.OP_dload_1:
				case OPCodes.OP_dload_2:
				case OPCodes.OP_dload_3:
					skip = 1;
				case OPCodes.OP_iload_0:
				case OPCodes.OP_iload_1:
				case OPCodes.OP_iload_2:
				case OPCodes.OP_iload_3:
				case OPCodes.OP_fload_0:
				case OPCodes.OP_fload_1:
				case OPCodes.OP_fload_2:
				case OPCodes.OP_fload_3:
				case OPCodes.OP_aload_0:
				case OPCodes.OP_aload_1:
				case OPCodes.OP_aload_2:
				case OPCodes.OP_aload_3:
					index = (opcode - 2) % 4;
					push(loadLocal(index), skip);
					break;

				case OPCodes.OP_laload:
				case OPCodes.OP_daload:
					skip = 1;
				case OPCodes.OP_iaload:
				case OPCodes.OP_faload:
				case OPCodes.OP_aaload:
				case OPCodes.OP_caload:
				case OPCodes.OP_saload:
				case OPCodes.OP_baload: {
					int idx = popi();
					ArrayRepr arrayRef = (ArrayRepr) pop();
					push(arrayRef.get(idx), skip);
					break;
				}

				case OPCodes.OP_istore:
				case OPCodes.OP_lstore:
				case OPCodes.OP_fstore:
				case OPCodes.OP_dstore:
				case OPCodes.OP_astore: {
					index = nextByte();
					storeLocal(index, pop());
					break;
				}

				case OPCodes.OP_lstore_0:
				case OPCodes.OP_lstore_1:
				case OPCodes.OP_lstore_2:
				case OPCodes.OP_lstore_3:
				case OPCodes.OP_dstore_0:
				case OPCodes.OP_dstore_1:
				case OPCodes.OP_dstore_2:
				case OPCodes.OP_dstore_3:
					skip = 1;
				case OPCodes.OP_istore_0:
				case OPCodes.OP_istore_1:
				case OPCodes.OP_istore_2:
				case OPCodes.OP_istore_3:
				case OPCodes.OP_fstore_0:
				case OPCodes.OP_fstore_1:
				case OPCodes.OP_fstore_2:
				case OPCodes.OP_fstore_3:
				case OPCodes.OP_astore_0:
				case OPCodes.OP_astore_1:
				case OPCodes.OP_astore_2:
				case OPCodes.OP_astore_3: {
					index = (opcode - 3) % 4;
					storeLocal(index, pop(skip));
					break;
				}

				case OPCodes.OP_lastore:
				case OPCodes.OP_dastore:
					skip = 1;
				case OPCodes.OP_iastore:
				case OPCodes.OP_fastore:
				case OPCodes.OP_aastore: {
					Object val = pop(skip);
					int idx = popi();
					ArrayRepr arrayRef = (ArrayRepr) pop();
					arrayRef.set(idx, val);
					break;
				}

				case OPCodes.OP_castore:
				case OPCodes.OP_sastore:
					mask = 0xFF;
				case OPCodes.OP_bastore: {
					mask = mask << 8;
					mask = mask | 0xFF;

					int val = (Integer) pop(skip);
					int idx = popi();
					ArrayRepr arrayRef = (ArrayRepr) pop();

					arrayRef.set(idx, (int) (val & mask));
					break;
				}

					// Stack management
				case OPCodes.OP_pop2:
					pop();
				case OPCodes.OP_pop:
					pop();
					break;
				case OPCodes.OP_dup: {
					Object v = pop();
					push(v).push(v);
					break;
				}
				case OPCodes.OP_dup_x1: {
					Object v1 = pop(), v2 = pop();
					push(v1).push(v2).push(v1);
					break;
				}
				case OPCodes.OP_dup_x2: {
					Object v1 = pop(), v2 = pop(), v3 = pop();
					push(v1).push(v3).push(v2).push(v1);
					break;
				}
				case OPCodes.OP_dup2: {
					Object v1 = pop(), v2 = pop();
					push(v2).push(v1).push(v2).push(v1);
					break;
				}

				case OPCodes.OP_dup2_x1: {
					Object v1 = pop(), v2 = pop(), v3 = pop();
					push(v2).push(v1).push(v3).push(v2).push(v1);
					break;
				}
				case OPCodes.OP_dup2_x2: {
					Object v1 = pop(), v2 = pop(), v3 = pop(), v4 = pop();
					push(v2).push(v1).push(v4).push(v3).push(v2).push(v1);
					break;
				}
				case OPCodes.OP_swap: {
					Object v1 = pop(), v2 = pop();
					push(v2).push(v1);
					break;
				}

					// Arithmetic
				case OPCodes.OP_iadd:
					pushi(popi() + popi());
					break;
				case OPCodes.OP_ladd:
					pushl(popl() + popl());
					break;
				case OPCodes.OP_fadd:
					pushf(popf() + popf());
					break;
				case OPCodes.OP_dadd:
					pushd(popd() + popd());
					break;

				case OPCodes.OP_isub:
					i = popi();
					pushi(popi() - i);
					break;
				case OPCodes.OP_lsub:
					l = popl();
					pushl(popl() - l);
					break;
				case OPCodes.OP_fsub:
					f = popf();
					pushf(popf() - f);
					break;
				case OPCodes.OP_dsub:
					d = popd();
					pushd(popd() - d);
					break;

				case OPCodes.OP_imul:
					pushi(popi() * popi());
					break;
				case OPCodes.OP_lmul:
					pushl(popl() * popl());
					break;
				case OPCodes.OP_fmul:
					pushf(popf() * popf());
					break;
				case OPCodes.OP_dmul:
					pushd(popd() * popd());
					break;

				// XXX Division by zero exception
				case OPCodes.OP_idiv:
					i = popi();
					pushi(popi() / i);
					break;

				case OPCodes.OP_ldiv:
					l = popl();
					pushl(popl() / l);
					break;
				case OPCodes.OP_fdiv:
					f = popf();
					pushf(popf() / f);
					break;
				case OPCodes.OP_ddiv:
					d = popd();
					pushd(popd() / d);
					break;

				case OPCodes.OP_irem:
					i = popi();
					pushi(popi() % i);
					break;
				case OPCodes.OP_lrem:
					l = popl();
					pushl(popl() % l);
					break;
				case OPCodes.OP_frem:
					f = popf();
					pushf(popf() % f);
					break;
				case OPCodes.OP_drem:
					d = popd();
					pushd(popd() % d);
					break;

				case OPCodes.OP_ineg:
					pushi(-popi());
					break;
				case OPCodes.OP_lneg:
					pushl(-popl());
					break;
				case OPCodes.OP_fneg:
					pushf(-popf());
					break;
				case OPCodes.OP_dneg:
					pushd(-popd());
					break;

				case OPCodes.OP_ishl:
					i = popi();
					pushi(popi() << i);
					break;
				case OPCodes.OP_lshl:
					i = popi();
					pushl(popl() << i);
					break;
				case OPCodes.OP_ishr:
					i = popi();
					pushi(popi() >> i);
					break;
				case OPCodes.OP_lshr:
					i = popi();
					pushl(popl() >> i);
					break;

				case OPCodes.OP_iushr:
					i = popi();
					pushi(popi() >>> i);
					break;
				case OPCodes.OP_lushr:
					i = popi();
					pushl(popl() >>> i);
					break;

				case OPCodes.OP_iand:
					pushi(popi() & popi());
					break;
				case OPCodes.OP_land:
					pushl(popl() & popl());
					break;

				case OPCodes.OP_ior:
					pushi(popi() | popi());
					break;
				case OPCodes.OP_lor:
					pushl(popl() | popl());
					break;

				case OPCodes.OP_ixor:
					pushi(popi() ^ popi());
					break;
				case OPCodes.OP_lxor:
					pushl(popl() ^ popl());
					break;

				case OPCodes.OP_iinc:
					index = nextByte();
					storeLocal(index, (Integer) loadLocal(index)
							+ (int) (byte) nextByte());
					break;

				case OPCodes.OP_i2l:
					pushl(popi().longValue());
					break;
				case OPCodes.OP_i2f:
					pushf(popi().floatValue());
					break;
				case OPCodes.OP_i2d:
					pushd(popi().doubleValue());
					break;
				case OPCodes.OP_l2i:
					pushi(popl().intValue());
					break;
				case OPCodes.OP_l2f:
					pushf(popl().floatValue());
					break;
				case OPCodes.OP_l2d:
					pushd(popl().doubleValue());
					break;
				case OPCodes.OP_f2i:
					pushi(popf().intValue());
					break;
				case OPCodes.OP_f2l:
					pushl(popf().longValue());
					break;
				case OPCodes.OP_f2d:
					pushd(popf().doubleValue());
					break;
				case OPCodes.OP_d2i:
					pushi(popd().intValue());
					break;
				case OPCodes.OP_d2l:
					pushl(popd().longValue());
					break;
				case OPCodes.OP_d2f:
					pushf(popd().floatValue());
					break;
				case OPCodes.OP_i2b:
					pushi((int) (byte) (int) popi());
					break;
				case OPCodes.OP_i2c:
					pushi(popi() & 0xFFFF);
					break;
				case OPCodes.OP_i2s:
					pushi((int) (short) (int) popi());
					break;

				// Comparison
				case OPCodes.OP_lcmp:
					l = popl();
					pushi(popl().compareTo(l));
					break;
				case OPCodes.OP_fcmpl:
				case OPCodes.OP_fcmpg:
					f = popf();
					pushi(popf().compareTo(f));
					break;
				case OPCodes.OP_dcmpl:
				case OPCodes.OP_dcmpg:
					d = popd();
					pushi(popd().compareTo(d));
					break;

				// Control flow
				case OPCodes.OP_ifeq:
					addr = nextShort();
					if (popi() == 0)
						pc = ppc + addr;
					break;
				case OPCodes.OP_ifne:
					addr = nextShort();
					if (popi() != 0)
						pc = ppc + addr;
					break;
				case OPCodes.OP_iflt:
					addr = nextShort();
					if (popi() < 0)
						pc = ppc + addr;
					break;
				case OPCodes.OP_ifge:
					addr = nextShort();
					if (popi() >= 0)
						pc = ppc + addr;
					break;
				case OPCodes.OP_ifgt:
					addr = nextShort();
					if (popi() > 0)
						pc = ppc + addr;
					break;
				case OPCodes.OP_ifle:
					addr = nextShort();
					if (popi() <= 0)
						pc = ppc + addr;
					break;
				case OPCodes.OP_if_icmpeq:
					addr = nextShort();
					if (popi() == popi())
						pc = ppc + addr;
					break;
				case OPCodes.OP_if_icmpne:
					addr = nextShort();
					if (popi() != popi())
						pc = ppc + addr;
					break;
				case OPCodes.OP_if_icmplt:
					addr = nextShort();
					if (popi() > popi())
						pc = ppc + addr;
					break;
				case OPCodes.OP_if_icmpge:
					addr = nextShort();
					if (popi() <= popi())
						pc = ppc + addr;
					break;
				case OPCodes.OP_if_icmpgt:
					addr = nextShort();
					if (popi() < popi())
						pc = ppc + addr;
					break;
				case OPCodes.OP_if_icmple:
					addr = nextShort();
					if (popi() >= popi())
						pc = ppc + addr;
					break;
				case OPCodes.OP_if_acmpeq:
					addr = nextShort();
					if (pop() == pop())
						pc = ppc + addr;
					break;
				case OPCodes.OP_if_acmpne:
					addr = nextShort();
					if (pop() != pop())
						pc = ppc + addr;
					break;
				case OPCodes.OP_goto:
					pc = ppc + nextShort();
					break;

				case OPCodes.OP_ifnull:
					addr = nextShort();
					if (pop() == null)
						pc = ppc + addr;
					break;
				case OPCodes.OP_ifnonnull:
					addr = nextShort();
					if (pop() != null)
						pc = ppc + addr;
					break;
				case OPCodes.OP_goto_w:
					pc = ppc + nextInt();
					break;

				// subroutine call
				case OPCodes.OP_jsr:
					push(pc + 2);
					pc = ppc + nextShort();
					break;
				case OPCodes.OP_jsr_w:
					push(pc + 4);
					pc = ppc + nextInt();
					break;
				case OPCodes.OP_ret:
					pc = (Integer) loadLocal(nextByte());
					break;

				// switch
				case OPCodes.OP_tableswitch: {
					// skip padding and read the three integers
					pc = ((pc + 3) >> 2) << 2;
					int def = nextInt(), low = nextInt(), high = nextInt();
					int tpc = pc;
					index = popi();

					// default branch
					if (index < low || index > high)
						pc = def;

					// extract the address from the table and jump
					pc = tpc + index - low;
					pc = ppc + nextByte();
					break;
				}

				case OPCodes.OP_lookupswitch: {
					pc = ((pc + 3) >> 2) << 2;
					int def = nextInt(), npairs = nextInt();
					int key = popi(), found = 0;

					for (i = 0; i < npairs; i++) {
						if (key == nextInt()) {
							pc = nextInt();
							found = 1;
							break;
						}
						pc += 4; // skip the offset offset
					}

					if (found == 0)
						pc = def;
					break;
				}

				case OPCodes.OP_anewarray: {
					JClassConstant cst = crtClass.getClassConstant(nextShort());
					JClass jc = jcl.getClassByConstant(cst);
					push(ObjectFactory.newArray(jc.getName(), popi()));
					break;
				}

				case OPCodes.OP_newarray: {
					int code = nextByte();
					String type = JType.getArrayElementType(code);
					push(ObjectFactory.newArray(type, popi()));
					break;
				}

				case OPCodes.OP_multianewarray: {
					String arrayClassName = crtClass.getClassName(nextShort());

					int[] dims = new int[nextByte()];
					for (i = dims.length - 1; i >= 0; i--)
						dims[i] = popi();
					push(newMultiArray(arrayClassName, dims, 0));
					break;
				}

				case OPCodes.OP_arraylength:
					pushi(((ArrayRepr) pop()).length());
					break;

				// new object
				case OPCodes.OP_new: { // index of the classname
					JClassConstant cst = crtClass.getClassConstant(nextShort());
					JClass jc = jcl.getClassByConstant(cst);
					push(ObjectFactory.newObject(jc));
					break;
				}

					// field access
				case OPCodes.OP_getstatic: {
					JMemberConstant fld = crtClass.getMember(nextShort());
					Object val = StaticMembers.getStaticField(fld);
					push(val, fld.getSize() - 1);
					break;
				}
				case OPCodes.OP_putstatic: {
					JMemberConstant fld = crtClass.getMember(nextShort());
					Object val = peek(fld.getSize());
					StaticMembers.putStaticField(fld, val);
					pop(fld.getSize() - 1);
					break;
				}

				case OPCodes.OP_getfield: {
					JMemberConstant fld = crtClass.getMember(nextShort());
					Object value = popo().getField(fld);
					push(value, fld.getSize() - 1);
					break;
				}
				case OPCodes.OP_putfield: {
					JMemberConstant fld = crtClass.getMember(nextShort());
					Object val = pop(fld.getSize() - 1);
					ObjectRepr obj = popo();
					obj.putField(fld, val);
					break;
				}

					/**
					 * Method invocation
					 */
				case OPCodes.OP_invokestatic: {
					JMemberConstant m = crtClass.getMember(nextShort());
					JMethod cm = StaticMembers.dispatchMethod(m);
					call(cm, jcl.getClassByConstant(m.getClassConstant()));
					break;
				}

				case OPCodes.OP_invokevirtual:
				case OPCodes.OP_invokeinterface: {
					JMemberConstant m = crtClass.getMember(nextShort());
					ObjectRepr obj = (ObjectRepr) peek(m.getArgsSize());
					JMethod dm = obj.dispatchMethod(m);
					call(dm, obj.getJClass());

					pc += (opcode == OPCodes.OP_invokeinterface) ? 2 : 0;
					break;
				}

				case OPCodes.OP_invokespecial: {
					JMemberConstant m = crtClass.getMember(nextShort());
					ObjectRepr obj = (ObjectRepr) peek(m.getArgsSize());
					JMethod dm;
					if (isSuperclass(m.getClassName(), crtClass.getName()) == 1
							&& !m.getMemberName().startsWith("<")) {
						dm = obj.dispatchSuperMethod(m);
					} else {
						dm = obj.dispatchNonVirtualMethod(m);
					}
					call(dm, jcl.getClassByConstant(m.getClassConstant()));
					break;
				}

				case OPCodes.OP_lreturn:
				case OPCodes.OP_dreturn:
					skip = 1;
				case OPCodes.OP_ireturn:
				case OPCodes.OP_freturn:
				case OPCodes.OP_areturn: {
					Object retVal = pop(skip);
					ret();
					push(retVal, skip);
					break;
				}
				case OPCodes.OP_return:
					ret();
					break;

				case OPCodes.OP_instanceof: {
					String refName = crtClass.getClassName(nextShort());
					String crtName = ((ObjectRepr) pop()).getJClass().getName();
					pushi(isSuperclass(refName, crtName));
					break;
				}

					// Exception
				case OPCodes.OP_athrow:
					this.pc = ppc;
					// first time just signal it to the debugger, don't throw
					if (!exceptionSignaled) {
						exceptionSignaled = true;
						// XXX give some parameter to the constructor
						// XXX debugExecute(new JDWPExceptionEvent());
						return;
					}
					athrow();
					break;

				// break the execution and return to debugger
				case OPCodes.OP_breakpoint:
					breakpointReached();
					return;

					/*
					 * Implementation dependent opcode: - with argument 0 exists
					 * the interpeter
					 */
				case OPCodes.OP_impdep1: {
					byte b = (byte) nextByte();
					impdep1(b);
					if (b == OPCodes.IMPDEP1_exit) {
						this.suspendForDebug(new JDWPVMExitEvent(b));
						return;
					}
					break;
				}

				case OPCodes.OP_wide: // XXX maybe it is not even used
					throw new UnsupportedOperationException("WIDE");

				case OPCodes.OP_checkcast:
				case OPCodes.OP_monitorenter:
				case OPCodes.OP_monitorexit:
				case OPCodes.OP_xxxunusedxxx1:
				case OPCodes.OP_impdep2:
				default:
					break;
				}

				// XXX check stepping mode !!! 
				if (BreakpointManager.isSingleStepActive()) {
					// XXX give some meaningful parameter
					this.suspendForDebug(new JDWPSingleStepEvent(null));
				}
			}
		} catch (JClassNotLoadedException e) {
			this.pc = ppc;

			this.jcl.loadClassAndResume(e.className, new ClassLoadedListener() {
				@Override
				public void onClassLoaded(LinkedList<JClass> classes) {
					int sz = classes.size(), i = 0;
					JDWPEvent events[] = new JDWPEvent[sz];
					for (JClass c : classes) {
						events[i++] = DbgUtil.classPreparedEventFromJClass(c);
					}
					ExecutionEngine.this.suspendForDebug(events);
				}
			});
			return;
		}
	}

	
	/**
	 * Bootstraps the JVM inside some harcoded method that invokes "main"
	 * 
	 * @throws JClassNotLoadedException
	 */
	public void bootstrap(final String bootClassName) {
		this.crtClass = JClass.createBootClass(bootClassName);
		this.m = this.crtClass.getMethods().get(0);
		this.pc = 0;

		this.locals = 0;
		this.top = 1;

		this.jcl.loadSystemClasses(new ClassLoadedListener() {

			@Override
			public void onClassLoaded(LinkedList<JClass> classes) {
				int sz = classes.size() + 1, i = 1;
				JDWPEvent events[] = new JDWPEvent[sz];
				for (JClass c : classes) {
					events[i++] = DbgUtil.classPreparedEventFromJClass(c);
				}
				events[0] = new JDWPVMStartEvent();

				ExecutionEngine.this.suspendForDebug(events);
			}
		});
	}

	/**
	 * Init a string using a char array
	 */
	private JMethod stringInitMethod = null;

	public void initString(JStringConstant str) throws JClassNotLoadedException {
		ArrayRepr chars = str.getCharArray();

		JClass stringClass = null;
		stringClass = jcl.getClassByName(JClassLoader.STRING_CLASS_NAME);
		ObjectRepr newStr = ObjectFactory.newObject(stringClass);

		// not initialized by now, but we will call the constructor soon
		str.setStringObject(newStr);

		if (stringInitMethod == null) {
			for (JMethod m : stringClass.getMethods())
				if (m.getMemberName().equals(JMethod.STRING_INIT_METHOD_NAME))
					stringInitMethod = m;
		}

		/*
		 * Any possible exception is thrown before this point, hence the
		 * execution can be restarted safely without leaving the stack in an
		 * inconsistent state.
		 */
		push(newStr);
		push(chars);
		call(stringInitMethod, stringClass);
	}

	/**
	 * Function call (after dispatch has been done)
	 */
	public void call(JMethod m, JClass cls) {
		// System.out.println("Entering " + m.getFullName());
		if (m.isNative()) {
			callNative(m);
			return;
		}

		int newlocals = top - m.getArgsSize();
		top = newlocals + m.getCode().maxLocals;

		pushi(this.locals);
		pushi(this.pc);
		push(this.m);
		push(this.crtClass);
		this.locals = newlocals;

		this.pc = 0;
		this.m = m;
		this.crtClass = cls;
	}

	/**
	 * Function return
	 */
	private void ret() {
		if (m.getMemberName().equals(JMethod.CLINIT_METHOD_NAME)){
			crtClass.setStatus(Status.INITIALIZED);
		}
		
		int oldpos = locals;
		int crtpos = top;

		top = locals + this.m.getCode().maxLocals + 4;

		this.crtClass = (JClass) pop();
		this.m = (JMethod) pop();
		this.pc = popi();
		this.locals = popi();

		top = oldpos;
		Arrays.fill(stack, oldpos, crtpos, null);
		// System.out.println("Leaving " + m.getFullName());
	}

	/**
	 * Stack unwinding until an exception handler to match the thrown exception
	 * is found.
	 */
	private boolean exceptionSignaled = false;

	private void athrow() throws JClassNotLoadedException {
		ObjectRepr exn = (ObjectRepr) peek(1);
		boolean handled = false;

		while (true) {
			for (ExceptionDescriptor d : this.m.getCode().exns) {
				if (d.start_pc <= pc && pc <= d.end_pc) {
					if (isSuperclass(d.type.getName(), exn.getJClass()
							.getName()) == 1) {
						pc = d.handler_pc;
						handled = true;
					}
				}
			}
			if (handled)
				break;
			this.ret();
			// we do not handle uncaught exceptions
		}
		exceptionSignaled = false;
	}

	/**
	 * 
	 * Creation of a multidimensional array.
	 * 
	 * @param className
	 * @throws JClassNotLoadedException
	 */
	private Object newMultiArray(String className, int dims[], int off)
			throws JClassNotLoadedException {
		ArrayRepr a = ObjectFactory.newArray(className, dims[off]);

		if (off < dims.length - 1) {
			for (int i = 0; i < dims[off]; i++)
				a.set(i, newMultiArray(className.substring(1), dims, off + 1));
		}

		return a;
	}

	/**
	 * Determine if a class is the ancestor of the other.
	 * 
	 * TODO: Can be spedup by some pre-computation.
	 */
	private int isSuperclass(String namep, String namec)
			throws JClassNotLoadedException {
		if (JType.isReference(namec)) {
			if (namep.equals(namec))
				return 1;

			JClass cc = jcl.getClassByName(namec);

			if (cc.getSuperClass() == null) // Object
				return 1;

			if (isSuperclass(namep, cc.getSuperClass().getName()) == 1)
				return 1;

			for (JClassConstant ic : cc.getInterfaces()) {
				if (isSuperclass(namep, ic.getName()) == 1)
					return 1;
			}
		} else if (JType.isArray(namec)) {
			if (namep.equals("Cloneable")
					|| namep.equals("java/io/Serializable")
					|| namep.equals(JClassLoader.OBJECT_CLASS_NAME)) {
				return 1;
			}
			if (JType.isArray(namep)) {
				if (JType.isPrimitive(namep) && namep.equals(namec))
					// same primitive type
					return 1;
				if (isSuperclass(JType.elemType(namep), JType.elemType(namec)) == 1)
					return 1;
			}
		}
		return 0;
	}

	/**
	 * Implementation dependent opcode handling.
	 */
	private void impdep1(byte b) {
		switch (b) {
		case OPCodes.IMPDEP1_exit: {
			int exitCode = popi();
			if (GWT.isClient()) {
				Window.alert("" + exitCode);
			} else {
				System.out.println("Exited with code: " + exitCode);
			}
			break;
		}

		}
	}

	/*
	 * Read next value from code
	 */
	private int nextShort() {
		int ret = DataInputStream.readShort(m.getCode().bytecode, pc);
		pc += 2;
		return ret;
	}

	private int nextInt() {
		int ret = (int) DataInputStream.readUInt(m.getCode().bytecode, pc);
		pc += 4;
		return ret;
	}

	private int nextByte() {
		return (int) ((int) (m.getCode().bytecode[pc++]) & 0xFF);
	}

	/*
	 * Singleton instance
	 */
	private static ExecutionEngine inst = new ExecutionEngine();

	public static ExecutionEngine getInstance() {
		return inst;
	}

	/**
	 * Function to deal with the breakpoints
	 */
	private void breakpointReached() {
		// FIXME generate events
		// FIXME replace breakpoint instruction (if needed)
		// FIXME send events and wait
		// FIXME debugExecute(give some param to debugExecute);
		
		// XXX what happens the next time this is reached
	}

	/**
	 * Suspend the thread, sends the events to server and waits for commands.
	 * Unless the command tells it to resume, it takes another command.
	 * 
	 * When it is told to resume, it calls the debugExecute() again.
	 */
	public void suspendForDebug(JDWPEvent events[]) {
		DbgUtil.pollForCommands(events, new AsyncCallback<VMCommand>() {

			@Override
			public void onSuccess(VMCommand cmd) {
				switch (cmd.getType()) {
				case THREAD_RESUME:
					ExecutionEngine.this.execute();
					return;
				case THREAD_EXIT:
					return;
				case THREAD_SUSPEND:
					break;
				case FRAME_COUNT:
					ExecutionEngine.this.registerFrames(m, crtClass, pc, locals);
					// XXX
					break;
				default:
					// FIXME deal with the command

					// resume the waiting for commands
					ExecutionEngine.this.suspendForDebug();
				}
				ExecutionEngine.this.suspendForDebug();
				
			}

			@Override
			public void onFailure(Throwable caught) {
				System.out.println("suspended: " + caught.getMessage());
			}
		});
	}

	public void suspendForDebug(JDWPEvent e) {
		JDWPEvent events[] = { e };
		suspendForDebug(events);
	}

	public void suspendForDebug() {
		JDWPEvent events[] = new JDWPEvent[0];
		suspendForDebug(events);
	}

	/**
	 * Harcoded native methods - ugly hack
	 */
	public void callNative(JMethod m) {

		// java/lang/System/arraycopy(Ljava/lang/Object;ILjava/lang/Object;II)V
		if (m.getMemberName().startsWith("arraycopy")) {
			int length = popi();
			int destPos = popi();
			ArrayRepr dest = (ArrayRepr) popo();
			int srcPos = popi();
			ArrayRepr src = (ArrayRepr) popo();
			for (int i = 0; i < length; i++) {
				dest.set(destPos + i, src.get(srcPos + i));
			}
		} else if (m.getMemberName().startsWith("getPrimitiveClass")) {
			@SuppressWarnings("unused")
			ObjectRepr name = popo();
			push(null); // the class :-??
		} else if (m.getMemberName().startsWith("floatToRawIntBits")) {
			popf();
			pushi(0);
		} else if (m.getMemberName().startsWith("doubleToRawLongBits")) {
			popd();
			pushl(0L);
		} else {
			System.out.println("Native method not handled: " + m.getFullName());
		}
		// System.out.println("Native method exited: " + m.getFullName());
	}
}
